package chatsystem.ni;

import chatsystem.FileTransferStatusCB;
import chatsystem.util.BidiMap;
import chatsystem.util.BidiMap_TS;
import java.io.IOException;
import java.net.InetAddress;
import chatsystemTDa2.*;
import java.io.InputStream;

/** Interface avec le réseau.
 *  Non thread safe. 
 *  Doit être stoppé à l'arret de l'application. */
public class ChatNI
{
   /** Exception if joining the multicast group failed. */
   public ChatNI(FromNI b, String addr, int port) throws IOException
   {
      _backend = b;
      
      _groupAddr = InetAddress.getByName(addr);
      _sock = new Socket(_groupAddr, port);
      
      // version synchronisée (important)
      _addrMap = new BidiMap_TS<String, InetAddress>();
      
      _messageID = 0;
      
      _rec = new Receiver(this);
      _rec.start();
   }

/* To NI **********************************************************************/

   public void sendHello(String localNickname)
   {
      _localNick = localNickname;
      Hello h = new Hello(localNickname);
      _sock.sendTo(_groupAddr, h);
   }

   public void sendMessage(String peer, String message)
   {
      Send s = new Send(message, _messageID);
      _messageID++;
      
      _sock.sendTo(_addrMap.getByKey(peer), s);
   }
   
   public void sendGoodbye()
   {
      _sock.sendTo(_groupAddr, new Goodbye(_localNick));
   }
   
   /** Filename: nom de fichier affiché pour l'utilisateur d'en face. */
   public void startFileTransfer(InputStream file, String filename, 
                                 String peer, FileTransferStatusCB cb)
   {
      if (_currFileTransfer != null)
         throw new IllegalStateException("Only one file tranfer at a time.");
      
      _currFileTransfer = new FileSender(this, file, _addrMap.getByKey(peer), cb);
      _sock.sendTo(_addrMap.getByKey(peer), new FileRequest(filename));
   }
   
   /** Doit être appelée en réponse asynchrone à la requête de transfert. 
    *  'cb' peut être NULL si le transfert est refusé. */
   public void acceptFileTransfer(boolean accept, FileTransferStatusCB cb)
   {
      if (_currFileRecv == null)
         throw new IllegalStateException("No transfer to accept");
      if (_currFileRecv.isAlive())
         throw new IllegalStateException("Only one file transfer at a time.");
      
      InetAddress peer = _currFileRecv.getExpectedAddr();
      String fn = _currFileRecv.getFileName();
      if (accept)
         _currFileRecv.start(cb);
      else {
         _currFileRecv.cancel(); // will NULL the field.
         try {_currFileRecv.join();} catch (InterruptedException e) {}
         _currFileRecv = null;
      }
      
      _sock.sendTo(peer, new FileResponse(accept, fn));
   }
   
   /** Peut être appelé alors qu'aucun transfert n'est en cours (la race 
    *  condition est trop dure à résoudre). */
   public void abortFileSend()
   {
      if (_currFileTransfer != null) {
         if (_currFileTransfer.isAlive())
            _currFileTransfer.stopSend();
         else
            _currFileTransfer.cancel();
            try {_currFileTransfer.join();} catch (InterruptedException e) {}
            _currFileTransfer = null;
      }
   }
   
   /** Peut être appelé alors qu'aucun transfert n'est en cours (la race 
    *  condition est trop dure à résoudre). */
   public void abortFileRecv()
   {
      if (_currFileRecv != null)
         _currFileRecv.stopRecv();
   }
   
   /** Mort propre des connexions réseau. à appeler en détruisant l'objet. */
   public void stop()
   {
      _sock.close(); // will kill Receiver thread
      
      // Avoid a race condition with transfer end.
      FileSender theFS = _currFileTransfer;
      if (theFS != null)
         theFS.stopSend();
      
      FileReceiver theFR = _currFileRecv;
      if (theFR != null)
         theFR.stopRecv();
   }

/* Package range, for subcomponents *******************************************/
   //friend class Receiver
   //friend class FileSender
   
   /** La réponse à notre (unique) requête de transfert est arrivée */
   void onFileResponse(boolean accept)
   {
      if (accept)
         _currFileTransfer.start();
      else {
         _currFileTransfer.cancel();
         try {_currFileTransfer.join();} catch (InterruptedException e) {}
         _currFileTransfer = null;
      }
   }
   
   void onFileTransferEnd()
   {
      _currFileTransfer = null;
   }
   
   /** Arrivée d'une requête de transfert. */
   void onFileRecv(InetAddress peer, String filename)
   {
      if (_currFileRecv == null) 
      {
         _currFileRecv = new FileReceiver(this, peer, filename);
         _backend.onFileRequest(_addrMap.getByValue(peer), filename);
      }
      else { // only one tranfer at a time
         _sock.sendTo(peer, new FileResponse(false, filename)); // sorry.
      }
   }
   
   void onFileRecvEnd()
   {
      _currFileRecv = null;
   }
   
   FromNI getBackend()                             {return _backend;}
   Socket getSocket()                              {return _sock;}
   String getMyNickname()                          {return _localNick;}
   BidiMap<String, InetAddress> getAddrMap()       {return _addrMap;}
   
/* Private ********************************************************************/
   
   private FromNI _backend;
   private Socket _sock;
   private InetAddress _groupAddr;
   private Receiver _rec;
   private String _localNick;
   private BidiMap<String, InetAddress> _addrMap;
   
   private FileSender _currFileTransfer; // may be NULL
   private FileReceiver _currFileRecv;   //
   
   // auto-increment at each message sent.
   private int _messageID;
}
